#include "tokenizer.h"
#include "errors.h"
#include "utills.h"
#include <iostream>
#include <algorithm>
#include <string>
#include <sstream>
#include <cctype>
#include <vector>
#include <list>
#include <cstdlib>
#include <map>
#include <limits>

namespace CCompiler
{
    FAS Table[fasEnd][256];

    typedef Token (* TokenMakeFunc)(FiniteAutomation &);
    TokenMakeFunc tokenMakers[fasEnd];

	std::map<std::string, TokenType> keywords;
	std::map<FAS, TokenType> directStateMap;
    std::map<FAS, std::string> errorsTable;

	char terminalsToChar_[256];

	char &terminalsToChar(char ch)
	{
	    return terminalsToChar_[(unsigned char)ch];
	}

    const char * OperationsFromTokenType[] =	{ "=", "==", "+", "-", "&", "|",
        "^", "&&", "||", "!", "+=", "-=", "*", "*=", "/", "/=", "%", "%=",
        "++", "--", "&=", "|=", "^=", "!=", ">", "<", ">=", "<=", "->", "<<",
        ">>", "?","~", "<<=", ">>=",
	};

	std::string StringAnalogOfTokenTypeOperation(TokenType tt)
	{
        return OperationsFromTokenType[tt-ttAssign];
	}

    std::ostream &operator<<(std::ostream &stream, const Token &token)
    {
        stream << '[' << token.line_ << ',' << token.pos_ << ']';
        stream << " Type: " << TokenTypeStrAnalogs[token.type_];
        stream << " T: " << token.text_;
        if(token.type_ == ttInteger)
            stream << " I: " << token.value_.i_;
        else if(token.type_ == ttChar)
            stream << " C: " << token.value_.c_ << " (" << token.value_.i_ << ')';
        else if(token.type_ == ttReal)
            stream << " F: " << token.value_.f_;
        else if(token.type_ == ttString || token.type_ == ttError)
            stream << " S: " << token.strvalue_;
        return stream << std::endl;
    }

	int octStrToInt(const std::string &str)
	{
		std::istringstream conv(str);
		int a;
		conv >> std::oct >> a;
		return a;
	}

	int hexStrToInt(const std::string &str)
	{
		std::istringstream conv(str);
		int a;
		conv >> std::hex >> a;
		return a;
	}

    /* Token Makers - pointers to this functions placed in the tokenMakers array */
    Token ignore(FiniteAutomation &) // never called, only indicates to skip token
    {
        return Token(ttNotToken, "", 0,0);
    }

    Token simpleMaker(FiniteAutomation &fa)
    {
		return Token(directStateMap[fa.state()], fa.processed_, fa.line(), fa.pos());
    }

    Token makeDec(FiniteAutomation &fa)
    {
        std::istringstream conv(fa.processed_);
        int a;
        conv >> a;
		return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), a);
    }

    Token makeHex(FiniteAutomation &fa)
    {
        return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), hexStrToInt(fa.processed_));
    }

    Token makeOct(FiniteAutomation &fa)
    {
        return Token(ttInteger, fa.processed_, fa.line(), fa.pos(), octStrToInt(fa.processed_));
    }

    Token makeFloat(FiniteAutomation &fa)
    {
        return Token(ttReal, fa.processed_, fa.line(), fa.pos(), (float)std::atof(fa.processed_.c_str()));
    }

    Token makeChar(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), fa.processed_[1]);
    }

    Token makeCharOct(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), octStrToInt(fa.processed_.substr(2, fa.processed_.length() - 3)));
    }

    Token makeCharHex(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), hexStrToInt(fa.processed_.substr(3, fa.processed_.length() - 4)));
    }

    Token makeCharWithSimpleTerminal(FiniteAutomation &fa)
    {
        return Token(ttChar, fa.processed_, fa.line(), fa.pos(), terminalsToChar(fa.processed_[2]));
    }

    Token makeString(FiniteAutomation &fa)
    {
		std::string strval;
		size_t fixed = 1, i = 1;
		for(; i < fa.processed_.length() - 1; ++i)
		{
			if(fa.processed_[i] != '\\') continue;
			strval += fa.processed_.substr(fixed, i-fixed);
			// now we got a slash!
			++i;
			if(SetOfChars('0', '7').in(fa.processed_[i]))
			{ // got an oct
				int oct_start = i;
				SetOfChars legal_oct_digits('0', '7');
				while(legal_oct_digits.in(fa.processed_[i])) ++i;
				strval += (unsigned char)(octStrToInt(fa.processed_.substr(oct_start, i - oct_start)));
				fixed = i;
				i--;
				continue;
			}
			else if(SetOfChars('x')('X').in(fa.processed_[i]))
			{ // got a hex
				++i;
				SetOfChars legal_hex_digits = SetOfChars ('0', '9')('A', 'F')('a', 'f');
				if(legal_hex_digits.in(fa.processed_[i]))
				{
					int hex_start = i;
					while(legal_hex_digits.in(fa.processed_[i])) ++i;
					strval += (unsigned char)(hexStrToInt(fa.processed_.substr(hex_start, i - hex_start)));
					fixed = i;
					i--;
					continue;
				}
			}
			strval += terminalsToChar(fa.processed_[i]);
			fixed = i+1;
		}
		strval += fa.processed_.substr(fixed, i - fixed - (i == fa.processed_.length() ? 1: 0));
        return Token(ttString, fa.processed_, fa.line(), fa.pos(), strval);
    }

    Token makeID(FiniteAutomation &fa)
    {
		std::map<std::string, TokenType>::iterator i;
        if((i = keywords.find(fa.processed_)) != keywords.end())
            return Token(i->second, fa.processed_, fa.line(), fa.pos());
        else
            return Token(ttIdentifier, fa.processed_, fa.line(), fa.pos());
    }

    /* */

    Tokenizer::Tokenizer(std::istream &stream)
        : stream_(stream), fa(stream_), current_(0)
    {
		if(!stream_) throw Errors::BrokenStream();
		nextToken();
    }


    void fillRow(FAS Row, FAS newval, SetOfChars columns)
    {
        for(std::list<char>::iterator i = columns.singles_.begin(); i != columns.singles_.end(); ++i)
            Table[Row][(int)*i] = newval;
        for(std::list<SetOfChars::Range>::iterator i = columns.ranges_.begin(); i != columns.ranges_.end(); ++i)
        {
            for(int a = i->first; a <= i->second; ++a)
                Table[Row][a] = newval;
        }
    }

    FiniteAutomation::FiniteAutomation(std::basic_istream<char> &stream):
        stream_(stream), line_(1), pos_(1), posInLine_(1), processed_(""),state_(fasStart)
    {
        processed_.reserve(255);
    }

    void FiniteAutomation::launch()
    {
        state_ = fasStart;
        ch_ = (char) stream_.get();
        pos_ = posInLine_;
        processed_.erase();
        if(stream_.eof())
            state_ = fasEof;
        else
        {
            while(Table[state_][(int)ch_])
            {
                processed_ += ch_;
                state_ = Table[state_][(int)ch_];
                if(ch_ == '\n')
                {
                    line_++;
                    posInLine_ = 0;
                }
                ch_ = (char) stream_.get();
				if(stream_.eof())
					break;
                posInLine_++;
            }
            stream_.putback(ch_);
        }
    }

    Token Tokenizer::getCurToken()
    {
        return token_;
    }

    Token Tokenizer::nextToken()
    {
		if(token_.type_ == ttError) return token_; // Prevent to read new tokens
        if(buff.size())
        {
            Token tok = buff.top();
            buff.pop();
            return token_ = tok;
        }
	relaunch:
		fa.launch();
		if(tokenMakers[fa.state()] == ignore) goto relaunch;
		else if(tokenMakers[fa.state()] == 0)
			throw Errors::TokenError(errorsTable[fa.state()], fa.line(), fa.pos(), fa.processed_);
		else
			token_ = tokenMakers[fa.state()](fa);
		return token_;
    }

    void Tokenizer::putBack(Token tok)
    {
        buff.push(tok);
    }
}

namespace __InitializeTokenizer
{
    void initialize()
    {
        using namespace CCompiler;
        std::map<std::string, TokenType> &kw = keywords;
        kw["return"] = ttReturn;
        kw["struct"] = ttStruct;
        kw["for"] = ttFor;
        kw["while"] = ttWhile;
        kw["if"] = ttIf;
        kw["switch"] = ttSwitch;
        kw["case"] = ttCase;
        kw["else"] = ttElse;
        kw["typedef"] = ttTypedef;
        kw["auto"] = ttAuto;
        kw["static"] = ttStatic;
        kw["extern"] = ttExtern;
        kw["sizeof"] = ttSizeof;
        kw["default"] = ttDefault;
        kw["continue"] = ttContinue;
        kw["break"] = ttBreak;
        kw["do"] = ttDo;
        kw["printf"] = ttPrintf;

        std::map<FAS, TokenType> &dm = directStateMap;
        dm[fasDot] = ttDot;
        dm[fasEllipsis] = ttEllipsis;
        dm[fasComma] = ttComma;
        dm[fasSemicolon] = ttSemicolon;
        dm[fasColon] = ttColon;
        dm[fasOpenParenthesis] = ttOpenParenthesis;
        dm[fasCloseParenthesis] = ttCloseParenthesis;
        dm[fasLeftBrace] = ttLeftBrace;
        dm[fasRightBrace] = ttRightBrace;
        dm[fasOpenBracket] = ttOpenBracket;
        dm[fasCloseBracket] = ttCloseBracket;
        dm[fasAssign] = ttAssign;
        dm[fasEqual] = ttEqual;
        dm[fasPlus] = ttPlus;
        dm[fasMinus] = ttMinus;
        dm[fasBAnd] = ttBAnd;
        dm[fasBOr] = ttBOr;
        dm[fasXor] = ttXor;
        dm[fasAnd] = ttAnd;
        dm[fasOr] = ttOr;
        dm[fasNot] = ttNot;
        dm[fasPlusAssign] = ttPlusAssign;
        dm[fasMinusAssign] = ttMinusAssign;
        dm[fasAsterisk] = ttAsterisk;
        dm[fasMulAssign] = ttMulAssign;
        dm[fasDiv] = ttDiv;
        dm[fasDivAssign] = ttDivAssign;
        dm[fasMod] = ttMod;
        dm[fasModAssign] = ttModAssign;
        dm[fasInc] = ttInc;
        dm[fasDecr] = ttDecr;
        dm[fasBAndAssign] = ttBAndAssign;
        dm[fasBOrAssign] = ttBOrAssign;
        dm[fasXorAssign] = ttXorAssign;
        dm[fasNotEqual] = ttNotEqual;
        dm[fasGreater] = ttGreater;
        dm[fasLess] = ttLess;
        dm[fasGreaterEqual] = ttGreaterEqual;
        dm[fasLessEqual] = ttLessEqual;
        dm[fasArrow] = ttArrow;
        dm[fasShL] = ttShL;
        dm[fasShR] = ttShR;
        dm[fasQuestion] = ttQuestion;
        dm[fasComplement] = ttComplement;
        dm[fasShLAssign] = ttShLAssign;
        dm[fasShRAssign] = ttShRAssign;
        dm[fasEof] = ttEof;

        std::map<FAS, std::string> &et = errorsTable;

        const char *MLCommentErrorMsg = "Eof meeted before closing the multiline comment";
        const char *CharNotClosedMsg = "Missing closing single quote";
        const char *BadStringMsg = "Bad String";

        et[fasStart] = "Unhandled Lexem";
        et[fasHexPrefix] = "Wrong Hex literal";
        et[fasDecwP] = "Wrong Float literal";
        et[fasFwE] = "Wrong Float with Exponent Literal";
        et[fasFwES] = "Wrong Float with Signed Exponent literal";
        et[fasDotDot] = "Two Dots is not correct lexem";
        et[fas2Q] = "Missing closing double quote";
        et[fas2QandC] = BadStringMsg;
        et[fas1QandC] = CharNotClosedMsg;
        et[fas1Q] = "Missing character";
        et[fas1QandSlash] = CharNotClosedMsg;
        et[fas1QandSimpleTerminal] = CharNotClosedMsg;
        et[fas1QandOct] = CharNotClosedMsg;
        et[fas1QandHex] = CharNotClosedMsg;
        et[fasMLCommentOpen] = MLCommentErrorMsg;
        et[fasMLCommentBody] = MLCommentErrorMsg;
        et[fasMLCommentStar] = MLCommentErrorMsg;
        et[fas2QandSlash] = BadStringMsg;

        typedef SetOfChars SOC;
        const char cmax = std::numeric_limits<char>::max(), cmin = std::numeric_limits<char>::min();

        // Other Tables
        //This table maps each valid state to specific Token Maker function
        std::fill(tokenMakers + fasDot, tokenMakers + fasWhiteSpace, simpleMaker);
        tokenMakers[fasId] = makeID;
        tokenMakers[fasZero] = makeDec;
        tokenMakers[fasHex] = makeHex;
        tokenMakers[fasOct] = makeOct;
        tokenMakers[fasDec] = makeDec;
        std::fill(tokenMakers + fasWhiteSpace, tokenMakers + fasMLCommentOpen, ignore);
        tokenMakers[fasChar] = makeChar;
        tokenMakers[fasCharWithSimpleTerminal] = makeCharWithSimpleTerminal;
        tokenMakers[fasCharWithHex] = makeCharHex;
        tokenMakers[fasCharWithOct] = makeCharOct;
        tokenMakers[fasString] = makeString;
        tokenMakers[fasFloat] = makeFloat;
        tokenMakers[fasFloatwExp] = makeFloat;
        tokenMakers[fasEof] = simpleMaker;
        // This table maps each simple escape-symbol to appropriate char value
        for(char i = cmin; i < cmax; ++i)
            terminalsToChar(i) = i;
        terminalsToChar(cmax) = cmax;
        terminalsToChar('t') = '\t';
        terminalsToChar('n') = '\n';
        terminalsToChar('v') = '\v';
        terminalsToChar('b') = '\b';
        terminalsToChar('r') = '\r';
        terminalsToChar('f') = '\f';
        terminalsToChar('a') = '\a';
        terminalsToChar('\\') = '\\';
        terminalsToChar('\?') = '\?';
        terminalsToChar('\'') = '\'';
        terminalsToChar('\"') = '\"';

        fillRow(fasStart, fasId, SOC('A', 'Z')('a', 'z')('$')('_'));
        fillRow(fasId, fasId, SOC('A', 'Z')('a', 'z')('$')('_')('0', '9'));
        // Zero
        fillRow(fasStart, fasZero, SOC('0'));
        // Oct
        fillRow(fasZero, fasOct, SOC('0','7'));
        fillRow(fasOct, fasOct, SOC('0','7'));
        // Hex
        fillRow(fasZero, fasHexPrefix, SOC('x')('X'));
        fillRow(fasHexPrefix, fasHex, SOC('0','9')('A','F')('a','f'));
        fillRow(fasHex, fasHex, SOC('0','9')('A','F')('a','f'));
        // Dec
        fillRow(fasStart, fasDec, SOC('1','9'));
        fillRow(fasDec, fasDec, SOC('0', '9'));
        fillRow(fasStart, fasDot, SOC('.'));
        // Ellipsis and Point
        fillRow(fasDot, fasDotDot, SOC('.'));
        fillRow(fasDotDot, fasEllipsis, SOC('.'));
        // float
        fillRow(fasDec, fasDecwP, SOC('.'));
        fillRow(fasZero, fasDecwP, SOC('.'));
        fillRow(fasDecwP, fasFloat, SOC('0', '9'));
        fillRow(fasDot, fasFloat, SOC('0', '9'));
        fillRow(fasFloat, fasFloat, SOC('0', '9'));
        // Float with Exponent
        fillRow(fasDec, fasFwE, SOC('E')('e'));
        fillRow(fasFloat, fasFwE, SOC('E')('e'));
        fillRow(fasFwE, fasFwES, SOC('+')('-'));
        fillRow(fasFwES, fasFloatwExp, SOC('0', '9'));
        fillRow(fasFloatwExp, fasFloatwExp, SOC('0', '9'));
        fillRow(fasFwE, fasFloatwExp, SOC('0', '9'));
        // Char
        // To simplify my life I've separate chars into 4 categories
        // Char itself, char with simple escape-symbol and chars with Oct or Hex escapes
        fillRow(fasStart, fas1Q, SOC('\''));
        fillRow(fas1Q, fas1QandC, SOC(cmin, cmax));
        fillRow(fas1Q, fas1QandSlash, SOC('\\'));
        fillRow(fas1QandSlash, fas1QandSimpleTerminal, SOC(cmin, cmax));
        fillRow(fas1QandSlash, fasError, SOC('\n'));
        fillRow(fas1QandSlash, fas1QandOct, SOC('0', '7'));
        fillRow(fas1QandOct, fas1QandOct, SOC('0', '7'));
        fillRow(fas1QandOct, fasCharWithOct, SOC('\''));
        fillRow(fas1QandSlash, fas1QandHex, SOC('x')('X'));
        fillRow(fas1QandHex, fas1QandHex, SOC('0', '9')('A','F')('a', 'f'));
        fillRow(fas1QandHex, fasCharWithHex, SOC('\''));
        fillRow(fas1QandSimpleTerminal, fasCharWithSimpleTerminal, SOC('\''));
        fillRow(fas1Q, fasError, SOC('\n'));
        fillRow(fas1QandC, fasChar, SOC('\''));
        // String
        fillRow(fasStart, fas2Q, SOC('"'));
        fillRow(fas2Q, fas2QandC, SOC(cmin, cmax));
        fillRow(fas2Q, fasString, SOC('"'));
        fillRow(fas2Q, fas2QandSlash, SOC('\\'));
        fillRow(fas2QandC, fas2QandC, SOC(cmin, cmax));
        fillRow(fas2QandC, fas2QandSlash, SOC('\\'));
        fillRow(fas2QandSlash, fas2QandC, SOC(cmin, cmax));
        fillRow(fas2QandSlash, fasError, SOC('\n'));
        fillRow(fas2Q, fasError, SOC('\n'));
        fillRow(fas2QandC, fasError, SOC('\n'));
        fillRow(fas2QandC, fasString, SOC('"'));

        // Delimeters
        // This section is quite easy
        fillRow(fasStart, fasComma, SOC(','));
        fillRow(fasStart, fasSemicolon, SOC(';'));
        fillRow(fasStart, fasColon, SOC(':'));
        fillRow(fasStart, fasOpenParenthesis, SOC('('));
        fillRow(fasStart, fasLeftBrace, SOC('{'));
        fillRow(fasStart, fasCloseParenthesis, SOC(')'));
        fillRow(fasStart, fasRightBrace, SOC('}'));
        fillRow(fasStart, fasOpenBracket, SOC('['));
        fillRow(fasStart, fasCloseBracket, SOC(']'));
        // Operations
        // And no problems with this
        fillRow(fasStart, fasAssign, SOC('='));
        fillRow(fasAssign, fasEqual, SOC('='));
        fillRow(fasStart, fasPlus, SOC('+'));
        fillRow(fasStart, fasMinus, SOC('-'));
        fillRow(fasStart, fasBAnd, SOC('&'));
        fillRow(fasStart, fasBOr, SOC('|'));
        fillRow(fasStart, fasXor, SOC('^'));
        fillRow(fasBAnd, fasAnd, SOC('&'));
        fillRow(fasBOr, fasOr, SOC('|'));
        fillRow(fasStart, fasNot, SOC('!'));
        fillRow(fasPlus, fasPlusAssign, SOC('='));
        fillRow(fasMinus, fasMinusAssign, SOC('='));
        fillRow(fasStart, fasAsterisk, SOC('*'));
        fillRow(fasAsterisk, fasMulAssign, SOC('='));
        fillRow(fasStart, fasDiv, SOC('/'));
        fillRow(fasDiv, fasDivAssign, SOC('='));
        fillRow(fasStart, fasMod, SOC('%'));
        fillRow(fasMod, fasModAssign, SOC('='));
        fillRow(fasPlus, fasInc, SOC('+'));
        fillRow(fasMinus, fasDecr, SOC('-'));
        fillRow(fasBAnd, fasBAndAssign, SOC('='));
        fillRow(fasBOr, fasBOrAssign, SOC('='));
        fillRow(fasXor, fasXorAssign, SOC('='));
        fillRow(fasNot, fasNotEqual, SOC('='));
        fillRow(fasStart, fasGreater, SOC('>'));
        fillRow(fasStart, fasLess, SOC('<'));
        fillRow(fasGreater, fasGreaterEqual, SOC('='));
        fillRow(fasLess, fasLessEqual, SOC('='));
        fillRow(fasMinus, fasArrow, SOC('>'));
        fillRow(fasLess, fasShL, SOC('<'));
        fillRow(fasGreater, fasShR, SOC('>'));
        fillRow(fasStart, fasQuestion, SOC('?'));
        fillRow(fasStart, fasComplement, SOC('~'));
        fillRow(fasShL, fasShLAssign, SOC('='));
        fillRow(fasShR, fasShRAssign, SOC('='));
        // not tokens
        // Indeed, we simply ignoring this states
        fillRow(fasStart, fasWhiteSpace, SOC(' '));
        fillRow(fasStart, fasWhiteSpace, SOC('\t'));
        fillRow(fasStart, fasCRLF, SOC('\n'));
        fillRow(fasDiv, fasLComment, SOC('/'));
        fillRow(fasLComment, fasLComment, SOC(cmin, cmax));
        fillRow(fasLComment, fasError, SOC('\n'));

        fillRow(fasDiv, fasMLCommentOpen, SOC('*'));
        fillRow(fasMLCommentOpen, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentOpen, fasMLCommentStar, SOC('*'));
        fillRow(fasMLCommentBody, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentBody, fasMLCommentStar, SOC('*'));
        fillRow(fasMLCommentStar, fasMLCommentBody, SOC(cmin, cmax));
        fillRow(fasMLCommentStar, fasMLComment, SOC('/'));
    }
}
